<html>
	<head>
		<title>Live Tabletop Automated Test</title>
		<link rel="stylesheet" href="test.css"/>
		<script type="text/javascript" src="../../jquery-1.11.0.min.js"></script>
		<script type="text/javascript" src="../../js/LT.js"></script>
		<script type="text/javascript" src="../../js/uploader.js"></script>
		<script type="text/javascript" src="test.js"></script>
		<script type="text/javascript">

// constants
TEST.USER1 = {password: "bar", email: "d@e.f", subscribed: 1}; // created and deleted to test admin scripts
TEST.USER2 = {password: "foo", email: "a@b.c", subscribed: 1}; // used for all of the non-admin scripts
TEST.USER3 = {password: "baz", email: "g@h.i", subscribed: 1}; // used when non-admin scripts require another user
TEST.CHARACTER_OWNER = function () {return {character: TEST.character, user: TEST.USER3.user};};
TEST.PIECE_SETTINGS = function () {return {piece: TEST.piece, image: "{}",
	name: "The Piece", character: 1, locked: 0, markers: "[]", color: "red"};},
TEST.ADMIN1 = {login: "nymduh", password: "draosup"};
TEST.ADMIN2 = {login: "admin2", password: "password2"};
TEST.INSTALL_ARGS = {
	admin_login: TEST.ADMIN1.login,
	admin_password: TEST.ADMIN1.password,
};

TEST.MESSAGE_IN = "From 1975 - 2010, [0 + 2010 - 1975] years passed.";
TEST.MESSAGE_OUT = 'From 1975 - 2010, <span class="roll">0 + 2010 - 1975 = 35</span> years passed.';

TEST.BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// list of tests
TEST.tests = [

	// INSTALL
	{group: "INSTALL"},
	{action: "../install.php", abort: true, args: TEST.INSTALL_ARGS, result: TEST.blank},
	{action: "../db_config.php", args: {}, result: TEST.exists},

	// clear session variables
	{action: "../Admin.logout.php", args: {}, result: TEST.exists},
	{action: "../User.logout.php", args: {}, result: TEST.exists},

	// ADMIN

	{group: "ADMIN.CREATE"},
	{action: "../Admin.create.php", args: TEST.ADMIN2, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.create.php", args: TEST.ADMIN2, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.LOGIN"},
	{action: "../Admin.login.php", args: {login: TEST.ADMIN2.login, password: "wrong"}, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: {login: "wrong", password: TEST.ADMIN2.password}, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: TEST.ADMIN2, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.LOGOUT"},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../Admin.create.php", args: TEST.ADMIN2, result: TEST.logged_out},

	{group: "ADMIN.DELETE"},
	{action: "../Admin.delete.php", args: {login: TEST.ADMIN2.login}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.delete.php", args: {login: TEST.ADMIN2.login}, result: TEST.blank},
	{action: "../Admin.login.php", args: TEST.ADMIN2, result: TEST.unauthorized},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.PASSWORD"},
	{action: "../Admin.password.php", args: {password: "newPassword"}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.password.php", args: {password: "newPassword"}, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../Admin.login.php", args: {login: TEST.ADMIN1.login, password: "newPassword"}, result: TEST.blank},
	{action: "../Admin.password.php", args: {password: TEST.ADMIN1.password}, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.USERS"},
	{action: "../Admin.users.php", args: {}, result: TEST.logged_out},
	{action: "../User.create.php", args: TEST.USER1, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [parse error: " + ajax.responseText + "]";}
		TEST.USER1.user = data.id;
		return "PASS";}},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.users.php", args: {}, result: TEST.one},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.FRIENDS"},
	{action: "../Admin.friends.php", args: {}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.friends.php", args: function () {return TEST.USER1;}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.requested.length + data.received.length + data.confirmed.length == 0) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.USER PASSWORD"},
	{action: "../Admin.userPassword.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER1, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.userPassword.php", args: function () {return TEST.USER1;}, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER1, result: TEST.exists},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.CAMPAIGNS"},
	{action: "../Admin.campaigns.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER1, result: TEST.exists},
	{action: "../Campaign.create.php", args: {name: "My Campaign"}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.campaign = data.id;
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.campaigns.php", args: {}, result: TEST.one},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.MESSAGES"},
	{action: "../Admin.messages.php", args: {}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.messages.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.zero},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.EXPIRE MESSAGES"},
	{action: "../Admin.expireMessages.php", args: {}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.expireMessages.php", args: {}, result: TEST.blank},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.DELETE CAMPAIGN"},
	{action: "../Admin.deleteCampaign.php", args: {}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.deleteCampaign.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.blank},
	{action: "../Admin.campaigns.php", args: {}, result: TEST.zero},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	{group: "ADMIN.DELETE USER"},
	{action: "../Admin.deleteUser.php", args: {}, result: TEST.logged_out},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.deleteUser.php", args: function () {return TEST.USER1;}, result: TEST.blank},
	{action: "../Admin.users.php", args: {}, result: TEST.zero},
	{action: "../Admin.campaigns.php", args: {}, result: TEST.zero},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},

	// U S E R

	{group: "USER.CREATE"},
	{action: "../User.create.php", args: TEST.USER2, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [parse error: " + ajax.responseText + "]";}
		TEST.USER2.user = data.id;
		return "PASS";}},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.users.php", args: function () {return TEST.USER2;}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id = TEST.USER2.user)
				TEST.USER2.resetCode = data[i].reset_code;
		return "PASS";}},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../User.password.php", args: function () {return TEST.USER2;}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.LOGIN"},
	{action: "../User.login.php", args: {email: TEST.USER2.email, password: "wrong"},
		result: TEST.unauthorized},
	{action: "../User.login.php", args: TEST.USER2, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.USER2.user = data.id;
		if (data.name === null
			&& data.color === null
			&& data.email == TEST.USER2.email
			&& data.subscribed == TEST.USER2.subscribed) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.campaigns.php", args: {}, result: TEST.exists},

	{group: "USER.LOGOUT"},
	{action: "../User.logout.php", args: {}, result: TEST.blank},
	{action: "../User.campaigns.php", args: {}, result: TEST.logged_out},

	{group: "USER.CHECK"},
	{action: "../User.check.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.check.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.USER2.user = data.id;
		if (data.name === null
			&& data.color === null
			&& data.email == TEST.USER2.email
			&& data.subscribed == TEST.USER2.subscribed) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.CHARACTERS"},
	{action: "../User.characters.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.characters.php", args: {}, result: TEST.zero},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.CAMPAIGNS"},
	{action: "../User.campaigns.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.campaigns.php", args: {}, result: TEST.zero},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.MAPS"},
	{action: "../User.maps.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.maps.php", args: {}, result: TEST.zero},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.SETTINGS"},
	{action: "../User.settings.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.settings.php", args: {name: "moe", color: "green", subscribed: 0}, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER2, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.name == "moe"
			&& data.color == "green"
			&& data.subscribed == 0) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER PASSWORD"},
	{action: "../User.password.php", args: {password: "newPassword"}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.password.php", args: {password: "newPassword"}, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},
	{action: "../User.login.php", args: {email: TEST.USER2.email, password: "newPassword"}, result: TEST.exists},
	{action: "../User.password.php", args: {password: TEST.USER2.password}, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER RESET PASSWORD"},
	{action: "../User.resetPassword.php", args: {email: TEST.USER2.email}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.users.php", args: function () {return TEST.USER2;}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id = TEST.USER2.user)
				TEST.USER2.resetCode = data[i].reset_code;
console.log(TEST.USER2.password);
		TEST.USER2.password += "_new";
console.log(TEST.USER2.password);
		return "PASS";}},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../User.password.php", args: function() {return TEST.USER2;}, result: TEST.blank},
	{action: "../User.login.php", args: function () {return TEST.USER2;}, result: TEST.exists},
	{action: "../User.password.php", args: function () {
console.log(TEST.USER2.password);
		TEST.USER2.password = TEST.USER2.password.slice(0, -("_new".length));
console.log(TEST.USER2.password);
		return {password: TEST.USER2.password};}, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.FRIENDS"},
	{action: "../User.friends.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.friends.php", args: {}, result: function (ajax) {
console.log(TEST.USER2.password);
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.requested.length + data.received.length + data.confirmed.length == 0) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.FRIEND"},
	{action: "../User.friend.php", args: {}, result: TEST.logged_out},
	{action: "../User.create.php", args: TEST.USER3, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [parse error: " + ajax.responseText + "]";}
		TEST.USER3.user = data.id;
		return "PASS";}},
	{action: "../User.login.php", args: TEST.USER3, result: TEST.unauthorized},
	{action: "../Admin.login.php", args: TEST.ADMIN1, result: TEST.blank},
	{action: "../Admin.users.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.USER3.user)
				TEST.USER3.resetCode = data[i].reset_code;
		return "PASS";}},
	{action: "../Admin.logout.php", args: {}, result: TEST.blank},
	{action: "../User.password.php", args: function () {return TEST.USER3;}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER3, result: TEST.exists},
	{action: "../User.logout.php", args: {}, result: TEST.blank},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	// friending self should fail silently
	{action: "../User.friend.php", args: {recipient: TEST.USER2.email}, result: TEST.blank},
	{action: "../User.friend.php", args: {recipient: TEST.USER3.email}, result: TEST.blank},
	{action: "../User.friends.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.requested.length + data.received.length + data.confirmed.length == 1) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "USER.UNFRIEND"},
	{action: "../User.unfriend.php", args: {}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../User.unfriend.php", args: {recipient: TEST.USER3.email}, result: TEST.blank},
	{action: "../User.friends.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.requested.length + data.received.length + data.confirmed.length == 0) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	// C A M P A I G N

	{group: "CAMPAIGN.CREATE"},
	{action: "../Campaign.create.php", args: {name: "My Campaign"}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.create.php", args: {name: "My Campaign"}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.campaign = data.id;
		return "PASS";}},
	{action: "../User.campaigns.php", args: {}, result: TEST.one},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.NAME"},
	{action: "../Campaign.name.php", args: function () {return {campaign: TEST.campaign, name: "Still My Campaign"};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.name.php", args: function () {return {campaign: TEST.campaign, name: "Still My Campaign"};},
		result: TEST.blank},
	{action: "../User.campaigns.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].campaign == TEST.campaign && data[0].name == "Still My Campaign") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.PRIVATE"},
	{action: "../Campaign.private.php", args: function () {return {campaign: TEST.campaign, 'private': 0};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.private.php", args: function () {return {campaign: TEST.campaign, 'private': 0};}, result: TEST.blank},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data['private'] == 0) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../Campaign.private.php", args: function () {return {campaign: TEST.campaign, 'private': 1};}, result: TEST.blank},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data['private'] == 1) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.READ"},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.id == TEST.campaign
			&& data.name == "Still My Campaign"
			&& data['private'] === true
			&& data.map === null
			&& JSON.stringify(data.turns) == JSON.stringify([])
			&& typeof(data.last_message) == "number"
			&& typeof(data.users_modified) == 'number') return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.MAP"},
	// TODO: use the id of a real map?
	{action: "../Campaign.map.php", args: function () {return {campaign: TEST.campaign, map: 1};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.map.php", args: function () {return {campaign: TEST.campaign, map: 1};}, result: TEST.blank},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.map == 1) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.TURNS"},
	{action: "../Campaign.turns.php", args: function () {return {campaign: TEST.campaign, turns: "[1,1,2,3,5,8,13,21]"};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.turns.php", args: function () {return {campaign: TEST.campaign, turns: "[1,1,2,3,5,8,13,21]"};},
		result: TEST.blank},
	{action: "../Campaign.read.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (JSON.stringify(data.turns) == "[1,1,2,3,5,8,13,21]") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.USERS"},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].id == 2
			&& data[0].permission == "owner"
			&& data[0].viewing === false
			&& data[0].avatar === null
			&& data[0].name == "moe") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.ARRIVE"},
	{action: "../Campaign.arrive.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.arrive.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.blank},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].id == 2
			&& data[0].permission == "owner"
			&& data[0].viewing === true
			&& data[0].avatar === null
			&& data[0].name == "moe") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.MESSAGE"},
	{action: "../Campaign.message.php", args: function () {return {campaign: TEST.campaign, avatar: 0, text: TEST.MESSAGE_IN};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.message.php", args: function () {return {campaign: TEST.campaign, avatar: 0, text: TEST.MESSAGE_IN};},
		result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.MESSAGES"},
	{action: "../Campaign.messages.php", args: function () {return {campaign: TEST.campaign, last_message: 0};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.messages.php", args: function () {return {campaign: TEST.campaign, last_message: 0};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].id == 1
			&& data[0].user == 2
			&& data[0].avatar === 0
			&& data[0].text == TEST.MESSAGE_OUT) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.AVATAR"},
	{action: "../Campaign.avatar.php", args: function () {return {campaign: TEST.campaign, avatar: 1};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.avatar.php", args: function () {return {campaign: TEST.campaign, avatar: 1};}, result: TEST.blank},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].id == 2
			&& data[0].permission == "owner"
			&& data[0].viewing === true
			&& data[0].avatar === 1
			&& data[0].name == "moe") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.LEAVE"},
	{action: "../Campaign.leave.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.leave.php", args: function () {return {campaign: TEST.campaign};}, result: TEST.blank},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data[0].id == 2
			&& data[0].permission == "owner"
			&& data[0].viewing === false
			&& data[0].avatar === 1
			&& data[0].name == "moe") return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.PERMISSION"},
	{action: "../Campaign.permission.php",
		args: function () {return {user: TEST.USER3.user, campaign: TEST.campaign, permission: "member"};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.permission.php",
		args: function () {return {user: TEST.USER3.user, campaign: TEST.campaign, permission: "member"};},
		result: TEST.blank},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.USER3.user
				&& data[i].permission == "member"
				&& data[i].viewing === false
				&& data[i].avatar === null
				&& data[i].name === null) return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CAMPAIGN.DELETE USER"},
	{action: "../Campaign.deleteUser.php",
		args: function () {return {user: TEST.USER3.user, campaign: TEST.campaign};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Campaign.deleteUser.php",
		args: function () {return {user: TEST.USER3.user, campaign: TEST.campaign};},
		result: TEST.blank},
	{action: "../Campaign.users.php", args: function () {return {campaign: TEST.campaign};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].user == TEST.USER3.user)
				return "FAIL [" + ajax.responseText + "]";
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "PRIVATE ROLL"},
	{action: "../private_roll.php", args: {text: TEST.MESSAGE_IN}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.text == TEST.MESSAGE_OUT) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},

	// M A P

	{group: "MAP.CREATE"},
	{action: "../Map.create.php", args: {type: "square", rows: 30, columns: 40, tile: 1, name: "My Map"},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.create.php", args: {type: "square", rows: 30, columns: 40, tile: 1, name: "My Map"},
		result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.map = data.id;
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.READ"},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.name == "My Map"
			&& data.type == "square"
			&& data.rows == 30
			&& data.columns == 40
			&& data.min_zoom == 0.25
			&& data.max_zoom == 4
			&& data.min_rotate == -180
			&& data.max_rotate == 180
			&& data.min_tilt == 30
			&& data.max_tilt == 90
			&& data.grid_thickness == 1
			&& data.wall_thickness == 3
			&& data.door_thickness == 3
			&& data.grid_color == "black"
			&& data.wall_color == "black"
			&& data.door_color == "white"
			&& typeof(data.piece_changes) == "number"
			&& typeof(data.tile_changes) == "number"
			&& data.tiles == Array(30 * 40 + 1).join("AA")
			&& data.walls == Array(31 * 42 + 1).join("A")
			&& data.fog == Array(Math.ceil(30 * 40 / 6) + 1).join("A")) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.PIECES"},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: TEST.zero},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.SETTINGS"},
	{action: "../Map.settings.php", args: function () {return {map: TEST.map, name: "Still My Map", type: "hex",
		min_zoom: 0.5, max_zoom: 2, min_rotate: -45, max_rotate: 45, min_tilt: 15, max_tilt: 45,
		grid_thickness: 0, grid_color: "orange", wall_thickness: 1, wall_color: "brown", door_thickness: 9, door_color: "green"};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.settings.php", args: function () {return {map: TEST.map, name: "Still My Map", type: "hex",
		min_zoom: 0.5, max_zoom: 2, min_rotate: -45, max_rotate: 45, min_tilt: 15, max_tilt: 45,
		grid_thickness: 0, grid_color: "orange", wall_thickness: 1, wall_color: "brown", door_thickness: 9, door_color: "green"};},
		result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.name == "Still My Map"
			&& data.type == "hex"
			&& data.rows == 30
			&& data.columns == 40
			&& data.min_zoom == 0.5
			&& data.max_zoom == 2
			&& data.min_rotate == -45
			&& data.max_rotate == 45
			&& data.min_tilt == 15
			&& data.max_tilt == 45
			&& data.grid_thickness == 0
			&& data.wall_thickness == 1
			&& data.door_thickness == 9
			&& data.grid_color == "orange"
			&& data.wall_color == "brown"
			&& data.door_color == "green"
			&& typeof(data.piece_changes) == "number"
			&& typeof(data.tile_changes) == "number"
			&& data.tiles == Array(30 * 40 + 1).join("AA")
			&& data.walls == Array(31 * 42 + 1).join("A")
			&& data.fog == Array(Math.ceil(30 * 40 / 6) + 1).join("A")) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.SIZE"},
	{action: "../Map.size.php",
		args: function () {return {map: TEST.map, left: -5, top: -5, right: 45, bottom: 35, tile: 0};},
		result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.size.php",
		args: function () {return {map: TEST.map, left: -5, top: -5, right: 45, bottom: 35, tile: 0};},
		result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.name == "Still My Map"
			&& data.type == "hex"
			&& data.rows == 40
			&& data.columns == 50
			&& data.min_zoom == 0.5
			&& data.max_zoom == 2
			&& data.min_rotate == -45
			&& data.max_rotate == 45
			&& data.min_tilt == 15
			&& data.max_tilt == 45
			&& data.grid_thickness == 0
			&& data.wall_thickness == 1
			&& data.door_thickness == 9
			&& data.grid_color == "orange"
			&& data.wall_color == "brown"
			&& data.door_color == "green"
			&& typeof(data.piece_changes) == "number"
			&& typeof(data.tile_changes) == "number"
			&& data.tiles == Array(40 * 50 + 1).join("AA")
			&& data.walls == Array(41 * 52 + 1).join("A")
			&& data.fog == Array(Math.ceil(40 * 50 / 6) + 1).join("A")) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.TILE"},
	{action: "../Map.tile.php", args: function () {return {map: TEST.map, x: 3, y: 2, tile: 2};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.tile.php", args: function () {return {map: TEST.map, x: 3, y: 2, tile: 2};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.tiles.substr((3 + 2 * 50) * 2, 2) == "AC") return "PASS";
		else return "FAIL [" + data.tiles.indexOf("AC") + " " + data.tiles + "]";}},
	{action: "../Map.tile.php", args: function () {return {map: TEST.map, x: 3, y: 2, tile: 128};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.tiles.substr((3 + 2 * 50) * 2, 2) == "CA") return "PASS";
		else return "FAIL [" + data.tiles.indexOf("CA") + " " + data.tiles + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.FOG"},
	{action: "../Map.fog.php", args: function () {return {map: TEST.map, x: 3, y: 2, fog: 1};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.fog.php", args: function () {return {map: TEST.map, x: 3, y: 2, fog: 1};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = Math.floor((3 + 2 * 50) / 6);
		var value = TEST.BASE64[Math.pow(2, 5 - (3 + 2 * 50) % 6)];
		if (data.fog[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.fog + "]";}},
	{action: "../Map.fog.php", args: function () {return {map: TEST.map, x: 3, y: 2, fog: 0};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = Math.floor((3 + 2 * 50) / 6);
		var value = TEST.BASE64[0];
		if (data.fog[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.fog + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.WALL"},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 's', type: 'wall'};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 's', type: 'wall'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[4 * 1];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 's', type: 'none'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[0];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 'e', type: 'wall'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[1 * 1];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 'se', type: 'door'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[1 * 2];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 'sw', type: 'door'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[1 * 2 + 16 * 2];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 'se', type: 'none'};}, result: TEST.blank},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 3, y: 2, side: 'sw', type: 'none'};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		var index = 3 + 2 * (50 + 2); var value = TEST.BASE64[0];
		if (data.walls[index] == value) return "PASS";
		else return "FAIL [" + value + " not found at " + index + " in " + data.walls + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.CHANGES"},
	{action: "../Map.changes.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.changes.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.piece_changes = data.piece_changes;
		TEST.tile_changes = data.tile_changes;
		return "PASS";}},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 0, y: 0, side: 'sw', type: 'wall'};}, result: TEST.blank},
	{action: "../Map.wall.php", args: function () {return {map: TEST.map, x: 0, y: 0, side: 'sw', type: 'none'};}, result: TEST.blank},
	{action: "../Map.changes.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.tile_changes == TEST.tile_changes + 2) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../Piece.create.php", args: function () {return {map: TEST.map, image: "{}"};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.piece = data.id;
		return "PASS";}},
	{action: "../Piece.delete.php", args: function () {return {piece: TEST.piece};}, result: TEST.blank},
	{action: "../Map.changes.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.piece_changes = TEST.piece_changes + 2) return "PASS";
		else return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.SHARE"},
	{action: "../Map.share.php", args: function () {return {map: TEST.map, user: TEST.USER3.user};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.share.php", args: function () {return {map: TEST.map, user: TEST.USER3.user};}, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.OWNERS"},
	{action: "../Map.owners.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.owners.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.USER3.user
				&& data[i].logged_in == false
				&& data[i].name === null
				&& data[i].color === null) return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.DELETE OWNER"},
	{action: "../Map.deleteOwner.php", args: function () {return {map: TEST.map, user: TEST.USER3.user};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.deleteOwner.php", args: function () {return {map: TEST.map, user: TEST.USER3.user};}, result: TEST.blank},
	{action: "../Map.owners.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.USER3.user)
				return "FAIL [" + ajax.responseText + "]";
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.FILL FOG"},
	{action: "../Map.fillFog.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.fillFog.php", args: function () {return {map: TEST.map};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.fog == Array(Math.ceil(40 * 50 / 6) + 1).join("/")) return "PASS";
		else return "FAIL [" + data.fog + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "MAP.CLEAR FOG"},
	{action: "../Map.clearFog.php", args: function () {return {map: TEST.map};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Map.clearFog.php", args: function () {return {map: TEST.map};}, result: TEST.blank},
	{action: "../Map.read.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		if (data.fog == Array(Math.ceil(40 * 50 / 6) + 1).join("A")) return "PASS";
		else return "FAIL [" + data.fog + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	// P I E C E

	{group: "PIECE.CREATE"},
	{action: "../Piece.create.php", args: function () {return {map: TEST.map, image: "{}"};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Piece.create.php", args: function () {return {map: TEST.map, image: "{}"};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.piece = data.id;
		return "PASS";}},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.piece
				&& JSON.stringify(data[i].image) == JSON.stringify({})
				&& data[i].name === null
				&& data[i].x == 0.5
				&& data[i].y == 0.5
				&& data[i].character === null
				&& data[i].locked == true
				&& JSON.stringify(data[i].markers) == JSON.stringify([])
				&& data[i].color == "gray") return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "PIECE.MOVE"},
	{action: "../Piece.move.php", args: function () {return {piece: TEST.piece, x: 2.5, y: 3.5};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Piece.move.php", args: function () {return {piece: TEST.piece, x: 2.5, y: 3.5};}, result: TEST.blank},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.piece
				&& JSON.stringify(data[i].image) == JSON.stringify({})
				&& data[i].name === null
				&& data[i].x == 2.5
				&& data[i].y == 3.5
				&& data[i].character === null
				&& data[i].locked == true
				&& JSON.stringify(data[i].markers) == JSON.stringify([])
				&& data[i].color == "gray") return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "PIECE.SETTINGS"},
	{action: "../Piece.settings.php", args: TEST.PIECE_SETTINGS, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Piece.settings.php", args: TEST.PIECE_SETTINGS, result: TEST.blank},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++) if (data[i].id == TEST.piece
			&& JSON.stringify(data[i].image) == JSON.stringify({})
			&& data[i].name == "The Piece"
			&& data[i].x == 2.5
			&& data[i].y == 3.5
			&& data[i].character == 1
			&& data[i].locked == false
			&& JSON.stringify(data[i].markers) == JSON.stringify([])
			&& data[i].color == "red")
				return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "PIECE.DELETE"},
	{action: "../Piece.delete.php", args: function () {return {piece: TEST.piece};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Piece.delete.php", args: function () {return {piece: TEST.piece};}, result: TEST.blank},
	{action: "../Map.pieces.php", args: function () {return {map: TEST.map};}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.piece)
				return "FAIL [" + ajax.responseText + "]";
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	// C H A R A C T E R

	{group: "CHARACTER.CREATE"},
	{action: "../Character.create.php", args: {name: "My Character", system: "The System", stats: "{}",
		notes: "These are the notes.", portrait: "{}", piece: "{}", color: "red"}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Character.create.php", args: {name: "My Character", system: "The System", stats: "{}",
		notes: "These are the notes.", portrait: "{}", piece: "{}", color: "red"}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		TEST.character = data.id;
		return "PASS";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CHARACTER.SETTINGS"},
	{action: "../Character.settings.php", args: function () {return {character: TEST.character,
		name: "Still My Character", system: "Some System", stats: "{\"gold\": 500, \"luck\": 7}",
		notes: "Nothing of note.", portrait: "{}", piece: "{}", color: "white"};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Character.settings.php", args: function () {return {character: TEST.character,
		name: "Still My Character", system: "Some System", stats: "{\"gold\": 500, \"luck\": 7}",
		notes: "[\"Nothing of note.\"]", portrait: "{}", piece: "{}", color: "white"};}, result: TEST.blank},
	{action: "../User.characters.php", args: {}, result: function (ajax) {
		try {var data = JSON.parse(ajax.responseText);}
		catch (e) {return "FAIL [not JSON: " + ajax.responseText + "]";}
		for (var i = 0; i < data.length; i++)
			if (data[i].id == TEST.character
				&& data[i].name == "Still My Character"
				&& data[i].system == "Some System"
				&& data[i].stats.gold == 500
				&& data[i].stats.luck == 7
				&& data[i].notes[0] == "Nothing of note."
				&& JSON.stringify(data[i].portrait) == JSON.stringify({})
				&& JSON.stringify(data[i].piece) == JSON.stringify({})
				&& data[i].color == "white")return "PASS";
		return "FAIL [" + ajax.responseText + "]";}},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CHARACTER.SHARE"},
	{action: "../Character.share.php", args: TEST.CHARACTER_OWNER, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Character.share.php", args: TEST.CHARACTER_OWNER, result: TEST.blank},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CHARACTER.OWNERS"},
	{action: "../Character.owners.php", args: function () {return {character: TEST.character};}, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Character.owners.php", args: function () {return {character: TEST.character};}, result: TEST.two},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

	{group: "CHARACTER.DELETE OWNER"},
	{action: "../Character.deleteOwner.php", args: TEST.CHARACTER_OWNER, result: TEST.logged_out},
	{action: "../User.login.php", args: TEST.USER2, result: TEST.exists},
	{action: "../Character.deleteOwner.php", args: TEST.CHARACTER_OWNER, result: TEST.blank},
	{action: "../Character.owners.php", args: function () {return {character: TEST.character};}, result: TEST.one},
	{action: "../User.logout.php", args: {}, result: TEST.blank},

];

$(function () {
	$.get("test.json", function (data) {
		$("input[name='location']").val(data.location);
		$("input[name='database']").val(data.database);
		$("input[name='username']").val(data.username);
		$("input[name='password']").val(data.password);
	});
	$("input[name='begin']").click(function () {
		TEST.INSTALL_ARGS.location = $("input[name='location']").val();
		TEST.INSTALL_ARGS.database = $("input[name='database']").val();
		TEST.INSTALL_ARGS.username = $("input[name='username']").val();
		TEST.INSTALL_ARGS.password = $("input[name='password']").val();
		TEST.start();
	});
});

		</script>
	</head>
	<body>
		<div><input type="text" name="location"/>URL of database server</div>
		<div><input type="text" name="database"/>database name</div>
		<div><input type="text" name="username"/>database user name</div>
		<div><input type="password" name="password"/>database password</div>
		<div><input type="button" name="begin" value="Begin Test"/></div>
		<div id="output"></div>
	</body>
</html>
